package impl

import (
	"context"
	//"errors"
	"fmt"
	"github.com/google/uuid"
	"github.com/opdss/go-helper/rand"
	"github.com/redis/go-redis/v9"
	"github.com/starmotioncf/common/filter"
	"github.com/starmotioncf/common/log"
	redis2 "github.com/starmotioncf/common/redis"
	sms2 "github.com/starmotioncf/common/sms"
	"github.com/starmotioncf/soutchatinternal"
	"github.com/starmotioncf/soutchatinternal/constant"
	"github.com/starmotioncf/soutchatinternal/constant/redis_key"
	"github.com/starmotioncf/soutchatinternal/dto"
	"github.com/starmotioncf/soutchatinternal/errcode"
	"github.com/starmotioncf/soutchatinternal/model"
	"github.com/starmotioncf/soutchatinternal/repository"
	"github.com/starmotioncf/soutchatinternal/service"
	"github.com/starmotioncf/soutchatpkg/notify"
	"github.com/starmotioncf/soutchatpkg/setting"
	"github.com/starmotioncf/soutchatpkg/sms"
	"github.com/starmotioncf/soutchatpkg/utils"
	"go.uber.org/zap"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
)

type smsService struct {
	*service.Service
	sms         *sms.Sms
	captchaRepo repository.CaptchaRepository
	countryRepo repository.CountryRepository
	settingRepo repository.SettingRepository
	langRepo    repository.LangRepository
}

func NewSmsService(s *service.Service, sms *sms.Sms, captchaRepo repository.CaptchaRepository,
	countryRepo repository.CountryRepository,
	settingRepo repository.SettingRepository,
	langRepo repository.LangRepository) service.SmsService {
	_s := &smsService{
		Service:     s,
		sms:         sms,
		captchaRepo: captchaRepo,
		countryRepo: countryRepo,
		settingRepo: settingRepo,
		langRepo:    langRepo,
	}
	return _s
}
func (s *smsService) SendCaptcha(ctx *gin.Context, req *dto.SmsSendCaptchaReq) (codeId string, err error) {
	mp := sms2.NewMobilePhone(req.Area, req.PhoneNumber)
	codeId = uuid.New().String()                    //验证码id
	code := fmt.Sprintf("%d", rand.Int(1000, 9999)) //随机验证码
	// //并发锁，客户端可能同时多个请求过来
	locker := redis2.NewLocker(redis_key.SmsCaptchaLock.ToString(mp.String()), s.RDB())
	if locker.Lock(time.Second*10) != nil {
		return "", errcode.ErrFrequentlyRequest
	}
	defer locker.UnLock()

	country, err := s.countryRepo.GetByArea(ctx, req.Area)
	if err != nil {
		return
	}
	if country == nil {
		return "", errcode.ErrRequest.New(s.langRepo.Get(ctx, "service.sms_not_support").TranslateTo(internal.GetLangFromCtx(ctx)))
	}
	// 获取短信配置，不分区
	smsSetting := s.getSetting(ctx, nil)

	//检查短信限制
	if err = s.checkLimit(ctx, req, country, smsSetting); err != nil {
		return "", err
	}

	lastRk := redis_key.SmsCaptchaLast.ToString(req.Stype)
	//发送成功后写入缓存
	defer func() {
		if err == nil {
			s.RDB().HSet(ctx, lastRk, mp.String(), fmt.Sprintf("%d%s", time.Now().Unix(), code))
			s.RDB().HExpire(ctx, lastRk, time.Duration(smsSetting.CaptchaExpired)*time.Second, mp.String())
			s.limitIncr(ctx, req, country)
		}
	}()

	//短信验证码发送时间限制
	lastSend := s.RDB().HGet(ctx, lastRk, mp.String())
	if lastSend.Err() == nil {
		lastVal := lastSend.Val()
		lastTime := int64(0)
		lastTime, err = strconv.ParseInt(lastVal[:10], 10, 64)
		if err != nil {
			return "", err
		}
		tmpTs := time.Now().Unix() - lastTime
		if tmpTs <= smsSetting.Interval {
			if utils.IsProd() {
				log.WithContext(ctx).Error(fmt.Sprintf("%ds内不能重复发送", smsSetting.Interval))
				return "", errcode.ErrAlert.New(s.langRepo.Get(ctx, "service.sms_limit_60s").TranslateTo(internal.GetLangFromCtx(ctx)))
			}
		}
		if tmpTs <= smsSetting.CaptchaExpired && lastVal[10:] != "" {
			code = lastVal[10:]
		}
	}
	var smsProvider sms.SMSProvider
	//客户端指定了whatsapp
	if req.SMSProvider == sms.SMSProviderWhatsapp {
		smsProvider = sms.SMSProviderWhatsapp
	} else {
		//国家没配置短信服务商，不发送短信
		if country.SmsProvider == 0 {
			return "", errcode.ErrRequest.New(s.langRepo.Get(ctx, "service.sms_not_support").TranslateTo(internal.GetLangFromCtx(ctx)))
		}
		smsProvider = country.SmsProvider
	}

	//发出短信
	err = s.sendCaptcha(ctx, smsProvider, mp, code, internal.GetLangFromCtx(ctx))
	//写入表
	status := constant.SmsCaptchaStatusSuccess
	errMsg := ""
	if err != nil {
		status = constant.SmsCaptchaStatusFail
		errMsg = err.Error()
	}
	m := &model.SmsCaptcha{
		Area:        req.Area,
		PhoneNumber: req.PhoneNumber,
		Mac:         internal.GetMacFromCtx(ctx),
		Ip:          ctx.ClientIP(),
		Stype:       req.Stype,
		Code:        code,
		CodeId:      codeId,
		CountryCode: country.Code,
		SmsProvider: smsProvider,
		Status:      status,
		ErrMsg:      errMsg,
		CreatedAt:   time.Now().Unix(),
	}
	err = s.captchaRepo.Create(ctx, m)
	return
}

// VerifyCaptcha 直接用验证码校验
func (s *smsService) VerifyCaptcha(ctx *gin.Context, req *dto.SmsVerifyCaptchaReq) error {
	//获取验证码校验
	captcha, err := s.captchaRepo.GetByCodeId(ctx, req.CodeId)
	if err != nil || captcha == nil {
		log.WithContext(ctx).Error("获取验证码失败(GetByCodeId)", zap.Error(err))
		return errcode.ErrInvalidCaptcha
	}
	// 获取短信配置，不分区
	smsSetting := s.getSetting(ctx, nil)
	//过期
	if time.Now().Unix()-captcha.CreatedAt > smsSetting.CaptchaExpired {
		return errcode.ErrInvalidCaptcha
	}
	//校验验证码
	if captcha.Code == req.Code && captcha.Area == req.Area && captcha.PhoneNumber == req.PhoneNumber {
		err = s.captchaRepo.UpdateVerifyCode(ctx, captcha.Id)
		if err != nil {
			log.WithContext(ctx).Error("更新验证码校验结果失败", zap.Error(err))
		}
		return nil
	}
	return errcode.ErrInvalidCaptcha
}

func (s *smsService) sendCaptcha(ctx *gin.Context, smsProvider sms.SMSProvider, mp *sms2.MobilePhone, code string, lang string) error {
	// 非生产环境不发送短信，假装发送成功
	if !utils.IsProd() {
		s.Notify.SendMsg(notify.DefaultTopic, notify.NotifyMsg(fmt.Sprintf("非生产环境发送验证码：%s[%s], (并未实际发送)", mp.String(), code)))
		return nil
	}
	//下面实际发送验证码
	var res sms2.Result
	var err error
	switch smsProvider {
	case sms.SMSProviderTencent, sms.SMSProviderAliyun, sms.SMSProviderWhatsapp:
		res, err = s.sms.SendCaptchaByTemplate(ctx, smsProvider, mp, fmt.Sprintf("%s", code))
	default:
		//梦网随意发送短信内容
		msgLang := s.langRepo.Get(ctx, "sms.captcha")
		msg := msgLang.TranslateTo(lang, code)
		res, err = s.sms.SendMsg(ctx, smsProvider, mp, msg)
	}
	log.WithContext(ctx).Info("短信发送结果", zap.String("result", internal.ToJson(res)), zap.Error(err))
	if err != nil {
		return s.parseSmsErrMsg(ctx, err, lang)
	}
	if len(res) == 0 {
		return errcode.ErrUnknown
	}
	if !res[0].Ok {
		return s.parseSmsErrMsg(ctx, res[0].Error, lang)
	}
	return nil
}

func (s *smsService) parseSmsErrMsg(ctx *gin.Context, err error, lang string) error {
	if err == nil {
		return nil
	}
	var _err *sms2.ErrResult
	var ok bool
	if _err, ok = err.(*sms2.ErrResult); !ok {
		return err
	}
	switch _err.Code {
	case "InvalidParameterValue.IncorrectPhoneNumber", "isv.MOBILE_NUMBER_ILLEGAL", -100277: //手机号吗格式错误
		return errcode.ErrMobilePhone
	case "LimitExceeded.PhoneNumberThirtySecondLimit": //频率太高
		return errcode.ErrAlert.New(s.langRepo.Get(ctx, "service.sms_limit_60s").TranslateTo(lang))
	case "LimitExceeded.PhoneNumberOneHourLimit", "LimitExceeded.PhoneNumberDailyLimit":
		return errcode.ErrFrequentlyRequest
	case "UnsupportedOperation.UnsupportedRegion", 9004: //不支持该区域发送
		return errcode.ErrAlert.New(s.langRepo.Get(ctx, "sms.not_support").TranslateTo(lang))
	case "TencentCloudSDKError": //sdk错误
		return errcode.ErrUnknown
	}
	return err
}

// checkLimit 检查短信发送限制
func (s *smsService) checkLimit(ctx *gin.Context, req *dto.SmsSendCaptchaReq, country *model.Country, smsSetting setting.Sms) error {
	if !s.limit(ctx, constant.SmsLimitTypeMac, internal.GetMacFromCtx(ctx), smsSetting.DayMacLimit) {
		return errcode.ErrFrequentlyRequest
	}
	if !s.limit(ctx, constant.SmsLimitTypeMobilePhone, req.PhoneNumber, smsSetting.DayPhoneLimit) {
		return errcode.ErrFrequentlyRequest
	}
	if !s.limit(ctx, constant.SmsLimitTypeIp, ctx.ClientIP(), smsSetting.DayIpLimit) {
		return errcode.ErrFrequentlyRequest
	}
	return nil
}

func (s *smsService) limit(ctx context.Context, limitType constant.SmsLimitType, val string, limitNum int) bool {
	return true
	//为0不检查限制
	if limitNum <= 0 {
		return true
	}
	rk := redis_key.SmsCaptchaLimit.ToString(limitType, time.Now().Format(time.DateOnly))
	res := s.RDB().HGet(ctx, rk, val)
	if res.Err() != nil && res.Err() != redis.Nil {
		log.WithContext(ctx).Error("captchaService.limit() hget  error:", zap.String("rk", rk), zap.Error(res.Err()))
		return false
	}
	currNum, _ := res.Int64()
	if currNum >= int64(limitNum) {
		log.WithContext(ctx).Debug("发送短信超出限制", zap.String("obj", val), zap.Int("limit", limitNum))
		return false
	}
	return true
}

func (s *smsService) limitIncr(ctx *gin.Context, req *dto.SmsSendCaptchaReq, country *model.Country) {
	date := time.Now().Format(time.DateOnly)
	rk1 := redis_key.SmsCaptchaLimit.ToString(constant.SmsLimitTypeIp, date)
	rk2 := redis_key.SmsCaptchaLimit.ToString(constant.SmsLimitTypeMac, date)
	rk3 := redis_key.SmsCaptchaLimit.ToString(constant.SmsLimitTypeMobilePhone, date)
	s.RDB().HIncrBy(ctx, rk1, ctx.ClientIP(), 1)
	s.RDB().HIncrBy(ctx, rk2, internal.GetMacFromCtx(ctx), 1)
	s.RDB().HIncrBy(ctx, rk3, req.PhoneNumber, 1)
	s.RDB().Expire(ctx, rk1, time.Hour*24)
	s.RDB().Expire(ctx, rk2, time.Hour*24)
	s.RDB().Expire(ctx, rk3, time.Hour*24)
}

func (s *smsService) getSetting(ctx context.Context, ent filter.Entity) setting.Sms {
	res, ok := s.settingRepo.Get(ctx, "service.sms", ent).(setting.Sms)
	if !ok {
		log.WithContext(ctx).Error("未找到[service.sms]的配置")
	}
	return res
}
